package com.gis.service.impl;

import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import cn.hutool.core.io.resource.ResourceUtil;
import com.gis.dto.OilspillDTO;
import com.gis.dto.RedtideDTO;
import com.gis.dto.YearAndSeasonDTO;
import com.gis.entity.StationData;
import com.gis.enums.QualityReportEnum;
import com.gis.exception.CustomException;
import com.gis.maps.commonMap;
import com.gis.service.*;
import com.gis.utils.PythonUtil;
import com.gis.vo.*;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.LineSeparator;
import com.itextpdf.text.pdf.draw.VerticalPositionMark;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import static com.gis.utils.CommonUtil.extractNumber;

@Slf4j
@Service
public class ModelPredictionServiceImpl implements ModelPredictionService {

    @Autowired
    private WaterQualityDataService waterQualityDataService;
    @Autowired
    private SedimentDataService sedimentDataService;
    @Autowired
    private IBiologyDataService biologyDataService;
    @Autowired
    private StationDataService stationDataService;
    @Autowired
    private PythonUtil pythonUtil;
    @Value("${python.path}")
    private String pythonPath;
    @Value("${python.n}")
    private int n;


    /**
     * 预测单参数
     * @param type
     * @param param
     * @param model
     * @param id
     * @return
     */
    @Override
    public PredictVO paramPredict(String type, String param, String model, int id){
        // 1 组装数据
        List<Float> data = new ArrayList<>();
        switch (type) {
            case "水质":
                data = pythonUtil.linearInterpolate(waterQualityDataService.getParamLastN(commonMap.getParam(param), n, id));
                break;
            case "沉积物":
                data = pythonUtil.linearInterpolate(sedimentDataService.getLastN(commonMap.getParam(param), n, id));
                break;
            case "生物质量":
                data = pythonUtil.linearInterpolate(biologyDataService.getLastN(commonMap.getParam(param), n, id));
                break;
            default:
                throw new CustomException("类型错误");
        }
        PredictVO predictVO = new PredictVO();
        YearAndSeasonDTO latestYearAndSeason = waterQualityDataService.getLatestYearAndSeason(id);
        BeanUtils.copyProperties(latestYearAndSeason, predictVO);
        if(!(data == null || data.isEmpty() || data.stream().allMatch(Objects::isNull))){
            Float pridict = getPridict(data, commonMap.getType(type), commonMap.getParam(param), commonMap.getModel(model));
            predictVO.setPredict(pridict);
        }
        return predictVO;
    }


    /**
     * 预测站点赤潮参数 不带站点信息
     * @param id
     * @return
     */
    @Override
    public RedtideDTO redtidePredict(int id) {
        // 获取5个数据进行预测 拼装最新的评价dto
        RedtideDTO redtideDTO = new RedtideDTO();
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        List<Future<?>> futures = new ArrayList<>();

        for (String param : commonMap.getRedtideParam()) {
            Future<?> future = executorService.submit(() -> {
                List<Float> lastN = pythonUtil.linearInterpolate(waterQualityDataService.getParamLastN(param, n, id));
                if (!(lastN == null || lastN.isEmpty() || lastN.stream().allMatch(Objects::isNull))) {
                    Float pridict = getPridict(lastN, "waterQuality", param, commonMap.getParamModel(param));
                    synchronized (redtideDTO) {
                        redtideDTO.setParam(param, pridict, redtideDTO);
                    }
                }
            });
            futures.add(future);
        }

        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
        return redtideDTO;
    }


    /**
     * 预测站点溢油参数 不带站点信息
     * @param id
     * @return
     */
    @Override
    public OilspillDTO oilspillPredict(int id) {
        // 获取5个数据进行预测 拼装最新的评价dto
        OilspillDTO oilspillDTO = new OilspillDTO();
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        List<Future<?>> futures = new ArrayList<>();

        for (String param : commonMap.getOilspillParam()) {
            Future<?> future = executorService.submit(() -> {
                List<Float> lastN = pythonUtil.linearInterpolate(waterQualityDataService.getParamLastN(param, n, id));
                if (!(lastN == null || lastN.isEmpty() || lastN.stream().allMatch(Objects::isNull))) {
                    Float pridict = getPridict(lastN, "waterQuality", param, commonMap.getParamModel(param));
                    synchronized (oilspillDTO) {
                        oilspillDTO.setParam(param, pridict, oilspillDTO);
                    }
                }
            });
            futures.add(future);
        }

        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
        return oilspillDTO;
    }

    /**
     * 所有站点赤潮预警预测
     *
     * @return
     */
    @Override
    public List<WarningReportVO> redtidePredictReport() {
        List<Integer> allIds = waterQualityDataService.selectAllIds();
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        List<CompletableFuture<WarningReportVO>> futures = allIds.stream()
                .filter(Objects::nonNull)
                .map(id -> CompletableFuture.supplyAsync(() -> {
                    RedtideDTO redtideDTO = redtidePredict(id);
                    // todo 判断数据是否足够
                    WarningReportVO warningReportVO = new WarningReportVO();
                    StationData stationData = stationDataService.getById(id);
                    BeanUtils.copyProperties(stationData, warningReportVO);
                    YearAndSeasonDTO latestYearAndSeason = waterQualityDataService.getLatestYearAndSeason(id);
                    warningReportVO.setYear(latestYearAndSeason.getNextYear());
                    warningReportVO.setSeason(latestYearAndSeason.getNextSeason());
                    warningReportVO.setLevel(redtideDTO.getResult().getName());
                    return warningReportVO;
                }, executorService))
                .collect(Collectors.toList());

        List<WarningReportVO> warningReportVOList = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        executorService.shutdown();
        return warningReportVOList;
    }

    @Override
    public List<WarningReportVO> oilspillPredictReport() {
        List<Integer> allIds = waterQualityDataService.selectAllIds();
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        List<CompletableFuture<WarningReportVO>> futures = allIds.stream()
                .filter(Objects::nonNull)
                .map(id -> CompletableFuture.supplyAsync(() -> {
                    OilspillDTO oilspillDTO = oilspillPredict(id);
                    WarningReportVO warningReportVO = new WarningReportVO();
                    StationData stationData = stationDataService.getById(id);
                    BeanUtils.copyProperties(stationData, warningReportVO);
                    YearAndSeasonDTO latestYearAndSeason = waterQualityDataService.getLatestYearAndSeason(id);
                    warningReportVO.setYear(latestYearAndSeason.getNextYear());
                    warningReportVO.setSeason(latestYearAndSeason.getNextSeason());
                    warningReportVO.setLevel(oilspillDTO.getResult().getName());
                    return warningReportVO;
                }, executorService))
                .collect(Collectors.toList());

        List<WarningReportVO> warningReportVOList = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        executorService.shutdown();
        return warningReportVOList;
    }

    @Override
    public void generateRedtidePredictReport(ByteArrayOutputStream outputStream, String screenshot) {
        try {
            Document document = new Document(PageSize.A4);
            PdfWriter.getInstance(document, outputStream);
            document.setMargins(70, 70, 70, 70); // 左、右、上、下边距
            document.open();
            /**
             * 中文字体
             * "STSong-Light"：宋体
             * "STSongStd-Light"：标准宋体
             */
            // 设置不同字体
            BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            BaseFont songFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            Font titleFont = new Font(songFont, 30, Font.BOLD, BaseColor.RED);  // 标题字体
            Font headerFont = new Font(baseFont, 15, Font.BOLD);              // 副标题字体
            Font normalFont = new Font(baseFont, 12, Font.NORMAL);             // 正文字体
            Font englishFont = FontFactory.getFont(FontFactory.TIMES_ROMAN, 12);

            // 添加标题
            Paragraph title = new Paragraph("小洋口海域赤潮灾害预警", titleFont);
            title.setAlignment(Element.ALIGN_CENTER);
            title.setSpacingBefore(50);
            title.setSpacingAfter(50);
            document.add(title);
            document.add(new Paragraph("\n"));

            // 添加副标题
            Paragraph header = new Paragraph();
            header.setSpacingAfter(20);
            header.setTabSettings(new TabSettings(30));
            Chunk leftText = new Chunk("江苏省如东小洋口旅游度假区管理办公室", headerFont);
            header.add(leftText);
            Chunk tab = new Chunk(new VerticalPositionMark());
            header.add(tab);
//            Chunk rightText = new Chunk(
//                    String.format("%d年%s", year, season),
//                    headerFont
//            );
//            header.add(rightText);
            document.add(header);
            document.add(new Paragraph("\n"));


            // 创建第一道横线
            LineSeparator line1 = new LineSeparator();
            line1.setLineColor(BaseColor.RED);
            line1.setLineWidth(1f);
            line1.setOffset(-2f); // 调整线条的位置
            // 创建第二道横线
            LineSeparator line2 = new LineSeparator();
            line2.setLineColor(BaseColor.RED);
            line2.setLineWidth(1f);
            line2.setOffset(-4f); // 调整线条的位置
            // 将横线添加到文档中
            document.add(line1);
            document.add(line2);


            // 添加水质评价情况
            Paragraph situation = new Paragraph("一、赤潮灾害预警情况", headerFont);
            situation.setSpacingBefore(20);
            document.add(situation);
            document.add(new Paragraph("\n"));

            // 获取评价数据
            List<WarningReportVO> reportVOS = redtidePredictReport();
            //站点排序
            reportVOS.sort(Comparator.comparingInt(vo -> extractNumber(vo.getName())));
            // 添加统计信息
            Map<String, Double> statistics = calculateWarningStatistics(reportVOS);
            Paragraph stats = new Paragraph();
            stats.add(new Chunk(String.format(
                    "赤潮预警情况：发生站位占 %.2f%%，不发生站位占 %.2f%%；\n",
                    statistics.get("发生"),
                    statistics.get("不发生")
            ), normalFont));
            document.add(stats);
            document.add(new Paragraph("\n"));

            // 添加站点具体情况标题
            Paragraph stationTitle = new Paragraph("二、站点具体情况", headerFont);
            stationTitle.setSpacingBefore(10);
            document.add(stationTitle);
            document.add(new Paragraph("\n"));

            // 创建站点数据表格
            PdfPTable table = new PdfPTable(4);  // 4列
            table.setWidthPercentage(100);
            float[] columnWidths = {10f, 30f, 30f, 30f};
            table.setWidths(columnWidths);
            // 添加表头
            addTableHeader(table, normalFont, new String[]{"序号", "监测站位", "赤潮发生情况", "预测时间"});
            // 添加数据行
            for (int i = 0; i < reportVOS.size(); i++) {
                WarningReportVO data = reportVOS.get(i);
                addTableRow(table, normalFont, new String[]{
                        String.valueOf(i + 1),
                        data.getName(),
                        data.getLevel(),
                        data.toString()
                });
            }
            document.add(table);


            if(screenshot!=null){
                // 处理Base64编码的图片
                String imageOnly = screenshot.split(",")[1];
                byte[] imageBytes = Base64.getDecoder().decode(imageOnly);
                // 添加评价情况
                document.newPage();
                Paragraph image = new Paragraph("三、赤潮灾害预警专题图", headerFont);
                image.setSpacingBefore(20);
                document.add(image);
                document.add(new Paragraph("\n"));
                // 缩放
                Image img = Image.getInstance(imageBytes);
                float imageWidth = img.getWidth();
                float imageHeight = img.getHeight();
                float pageWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
                float scaleFactor = pageWidth / imageWidth;
                img.scaleAbsolute(imageWidth * scaleFactor, imageHeight * scaleFactor);
                img.setAlignment(Element.ALIGN_CENTER);
                document.add(img);
                // 图例
                byte[] resource = ResourceUtil.readBytes("image/warning.png");
                Image img2 = Image.getInstance(resource);
                float imageWidth2 = img2.getWidth();
                float imageHeight2 = img2.getHeight();
                img2.scaleAbsolute((float) (imageWidth2 * 0.3), (float)(imageHeight2 * 0.3));
                // 调整图例图片的位置，使其与主图重叠，IText的坐标系是左下角为原点
                float mainImageLeftX = (document.getPageSize().getWidth() - imageWidth * scaleFactor) / 2;
                float scaledLegendWidth = (float)(imageWidth2 * 0.3);
                float offsetX = document.getPageSize().getWidth()-mainImageLeftX-scaledLegendWidth;
                float offsetY = document.getPageSize().getHeight() - imageHeight * scaleFactor - 125;
                img2.setAbsolutePosition(offsetX, offsetY);
                document.add(img2);
                // 添加评价情况
                Paragraph imageBottle = new Paragraph("赤潮灾害预警专题图\n", headerFont);
                imageBottle.setAlignment(Element.ALIGN_CENTER);
                imageBottle.setSpacingBefore(20);
                document.add(imageBottle);
                document.add(new Paragraph("\n"));
            }

            document.close();
        } catch (Exception e) {
            log.error("生成PDF报告失败", e);
            throw new CustomException("PDF生成失败");
        }
    }

    @Override
    public void generateOilspillPredictReport(ByteArrayOutputStream outputStream, String screenshot) {
        try {
            Document document = new Document(PageSize.A4);
            PdfWriter.getInstance(document, outputStream);
            document.setMargins(70, 70, 70, 70); // 左、右、上、下边距
            document.open();
            /**
             * 中文字体
             * "STSong-Light"：宋体
             * "STSongStd-Light"：标准宋体
             */
            // 设置不同字体
            BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            BaseFont songFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            Font titleFont = new Font(songFont, 30, Font.BOLD, BaseColor.RED);  // 标题字体
            Font headerFont = new Font(baseFont, 15, Font.BOLD);              // 副标题字体
            Font normalFont = new Font(baseFont, 12, Font.NORMAL);             // 正文字体
            Font englishFont = FontFactory.getFont(FontFactory.TIMES_ROMAN, 12);

            // 添加标题
            Paragraph title = new Paragraph("小洋口海域溢油风险预警", titleFont);
            title.setAlignment(Element.ALIGN_CENTER);
            title.setSpacingBefore(50);
            title.setSpacingAfter(50);
            document.add(title);
            document.add(new Paragraph("\n"));

            // 添加副标题
            Paragraph header = new Paragraph();
            header.setSpacingAfter(20);
            header.setTabSettings(new TabSettings(30));
            Chunk leftText = new Chunk("江苏省如东小洋口旅游度假区管理办公室", headerFont);
            header.add(leftText);
            Chunk tab = new Chunk(new VerticalPositionMark());
            header.add(tab);
//            Chunk rightText = new Chunk(
//                    String.format("%d年%s", year, season),
//                    headerFont
//            );
//            header.add(rightText);
            document.add(header);
            document.add(new Paragraph("\n"));


            // 创建第一道横线
            LineSeparator line1 = new LineSeparator();
            line1.setLineColor(BaseColor.RED);
            line1.setLineWidth(1f);
            line1.setOffset(-2f); // 调整线条的位置
            // 创建第二道横线
            LineSeparator line2 = new LineSeparator();
            line2.setLineColor(BaseColor.RED);
            line2.setLineWidth(1f);
            line2.setOffset(-4f); // 调整线条的位置
            // 将横线添加到文档中
            document.add(line1);
            document.add(line2);


            // 添加水质评价情况
            Paragraph situation = new Paragraph("一、溢油风险预警情况", headerFont);
            situation.setSpacingBefore(20);
            document.add(situation);
            document.add(new Paragraph("\n"));

            // 获取评价数据
            List<WarningReportVO> reportVOS = oilspillPredictReport();
            //站点排序
            reportVOS.sort(Comparator.comparingInt(vo -> extractNumber(vo.getName())));

            // 添加统计信息
            Map<String, Double> statistics = calculateWarningStatistics(reportVOS);
            Paragraph stats = new Paragraph();
            stats.add(new Chunk(String.format(
                    "溢油预警情况：发生站位占 %.2f%%，不发生站位占 %.2f%%；\n",
                    statistics.get("发生"),
                    statistics.get("不发生")
            ), normalFont));
            document.add(stats);
            document.add(new Paragraph("\n"));

            // 添加站点具体情况标题
            Paragraph stationTitle = new Paragraph("二、站点具体情况", headerFont);
            stationTitle.setSpacingBefore(10);
            document.add(stationTitle);
            document.add(new Paragraph("\n"));

            // 创建站点数据表格
            PdfPTable table = new PdfPTable(4);  // 4列
            table.setWidthPercentage(100);
            float[] columnWidths = {10f, 30f, 30f, 30f};
            table.setWidths(columnWidths);
            // 添加表头
            addTableHeader(table, normalFont, new String[]{"序号", "监测站位", "溢油发生情况", "预测时间"});
            // 添加数据行
            for (int i = 0; i < reportVOS.size(); i++) {
                WarningReportVO data = reportVOS.get(i);
                addTableRow(table, normalFont, new String[]{
                        String.valueOf(i + 1),
                        data.getName(),
                        data.getLevel(),
                        data.toString()
                });
            }
            document.add(table);


            if(screenshot!=null){
                // 处理Base64编码的图片
                String imageOnly = screenshot.split(",")[1];
                byte[] imageBytes = Base64.getDecoder().decode(imageOnly);
                // 添加评价情况
                document.newPage();
                Paragraph image = new Paragraph("三、溢油风险预警专题图", headerFont);
                image.setSpacingBefore(20);
                document.add(image);
                document.add(new Paragraph("\n"));
                // 缩放
                Image img = Image.getInstance(imageBytes);
                float imageWidth = img.getWidth();
                float imageHeight = img.getHeight();
                float pageWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
                float scaleFactor = pageWidth / imageWidth;
                img.scaleAbsolute(imageWidth * scaleFactor, imageHeight * scaleFactor);
                img.setAlignment(Element.ALIGN_CENTER);
                document.add(img);
                // 图例
                byte[] resource = ResourceUtil.readBytes("image/warning.png");
                Image img2 = Image.getInstance(resource);
                float imageWidth2 = img2.getWidth();
                float imageHeight2 = img2.getHeight();
                img2.scaleAbsolute((float) (imageWidth2 * 0.3), (float)(imageHeight2 * 0.3));
                // 调整图例图片的位置，使其与主图重叠，IText的坐标系是左下角为原点
                float mainImageLeftX = (document.getPageSize().getWidth() - imageWidth * scaleFactor) / 2;
                float scaledLegendWidth = (float)(imageWidth2 * 0.3);
                float offsetX = document.getPageSize().getWidth()-mainImageLeftX-scaledLegendWidth;
                float offsetY = document.getPageSize().getHeight() - imageHeight * scaleFactor - 125;
                img2.setAbsolutePosition(offsetX, offsetY);
                document.add(img2);
                // 添加评价情况
                Paragraph imageBottle = new Paragraph("溢油风险预警专题图\n", headerFont);
                imageBottle.setAlignment(Element.ALIGN_CENTER);
                imageBottle.setSpacingBefore(20);
                document.add(imageBottle);
                document.add(new Paragraph("\n"));
            }

            document.close();
        } catch (Exception e) {
            log.error("生成PDF报告失败", e);
            throw new CustomException("PDF生成失败");
        }
    }


    /**
     * 预测模型调用
     * @param data
     * @param type
     * @param param
     * @param model
     * @return
     */
    private Float getPridict(List<Float> data, String type, String param, String model){
        // 2 组装命令行
        if(model==null)
            model = "Seq2Seq";
        String modelName = type+"_"+param+"_"+model;
        List<String> command = new ArrayList<>();
        command.add("bash");
        command.add("base.sh");
        command.add("-m");
        command.add(modelName);
        for (Float value : data) {
            command.add(value.toString());
        }
        log.error(command.toString());
        // 4 执行命令获取返回数据
        try {
            File workingDirectory = new File(pythonPath);
            ProcessBuilder processBuilder = new ProcessBuilder(command);
            processBuilder.directory(workingDirectory);
            Process process = processBuilder.start();
            // 读取Python脚本的最后一个输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            String lastLine = null;
            while ((line = reader.readLine()) != null) {
                lastLine = line;
                log.info(lastLine);
            }
            // 等待进程结束，并获取退出状态码
            int exitCode = process.waitFor();
            if (exitCode == 0 && lastLine != null) {
                // 返回预测结果
                return Float.parseFloat(lastLine.trim());
            } else {
                throw new CustomException("Python脚本执行失败");
            }
        } catch (IOException | InterruptedException e) {
            throw new CustomException("文件读取异常");
        }
    }




    /**
     * 表格操作
     * @param table
     * @param font
     * @param headers
     */
    private void addTableHeader(PdfPTable table, Font font, String[] headers) {
        for (String header : headers) {
            PdfPCell cell = new PdfPCell(new Phrase(header, font));
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setPadding(5);
            table.addCell(cell);
        }
    }
    private void addTableRow(PdfPTable table, Font font, String[] rowData) {
        for (String data : rowData) {
            PdfPCell cell = new PdfPCell(new Phrase(data, font));
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setPadding(5);
            table.addCell(cell);
        }
    }


    private Map<String, Double> calculateWarningStatistics(List<WarningReportVO> data) {
        Map<String, Double> stats = new HashMap<>();
        int total = data.size();

        long first = data.stream().filter(d -> "发生".equals(d.getLevel())).count();
        long second = data.stream().filter(d -> "不发生".equals(d.getLevel())).count();

        stats.put("发生", first * 100.0 / total);
        stats.put("不发生", second * 100.0 / total);

        return stats;
    }
}
